package org.test4j.mock.faking.meta;

import g_asm.org.objectweb.asm.Type;
import lombok.Getter;
import org.test4j.mock.Invocation;
import org.test4j.mock.Mock;
import org.test4j.mock.MockUp;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * MockUp @Mock 方法
 */
public class FakeMethod {
    /**
     * {@link MockUp} 实例
     */
    public final AbstractFake fake;
    /**
     * {@link Mock} 方法元数据
     */
    public final MethodId meta;
    /**
     * {@link Mock} 方法
     */
    private Method mockMethod;

    @Getter
    private boolean hasMatchingRealMethod;
    /**
     * 被调用次数
     */
    private final AtomicInteger invocationCount = new AtomicInteger(0);
    /**
     * 当前Invocation
     */
    private ThreadLocal<Invocation> proceedingInvocation = new ThreadLocal<>();

    public FakeMethod(AbstractFake fake, MethodId meta) {
        this.fake = fake;
        this.meta = meta;
        this.hasMatchingRealMethod = false;
    }

    public Method findMockMethod() {
        if (this.mockMethod != null) {
            return this.mockMethod;
        }
        String[] types = meta.getParaTypeNames();
        Class aClass = fake.getClass();
        while (this.mockMethod == null && MockUp.class.isAssignableFrom(aClass) && !aClass.equals(MockUp.class)) {
            Method[] methods = aClass.getDeclaredMethods();
            for (Method method : methods) {
                if (!Objects.equals(meta.name, method.getName())) {
                    continue;
                }
                if (matchParas(method, types)) {
                    this.mockMethod = method;
                    break;
                }
            }
            aClass = aClass.getSuperclass();
        }
        if (this.mockMethod == null) {
            String desc = meta.name + "(" + String.join(", ", types) + ")";
            throw new IllegalArgumentException("No compatible method found: " + desc);
        } else {
            return this.mockMethod;
        }
    }

    private boolean matchParas(Method method, String[] types) {
        int count = method.getParameterCount();
        if (count != types.length) {
            return false;
        }
        Class[] pTypes = method.getParameterTypes();
        for (int index = 0; index < count; index++) {
            String metaTypeName = types[index];
            String methodParaType = Type.getType(pTypes[index]).getClassName();
            if (!Objects.equals(metaTypeName, methodParaType)) {
                return false;
            }
        }
        return true;
    }

    public boolean isProceeding() {
        FakeInvocation invocation = this.getProceedingInvocation();
        if (invocation != null && invocation.isProceeding()) {
            invocation.setProceeding(false);
            return true;
        } else {
            invocationCount.incrementAndGet();
            return false;
        }
    }

    public int getTimesInvoked() {
        return invocationCount.get();
    }

    public void setProceedingInvocation(Invocation invocation) {
        proceedingInvocation.set(invocation);
    }

    public FakeInvocation getProceedingInvocation() {
        return (FakeInvocation) proceedingInvocation.get();
    }

    /**
     * 设置当前正在执行的Invocation, 避免循环调用
     *
     * @param invocation
     */
    public void setProceedingInvocation4NonRecursive(Invocation invocation) {
        ((FakeInvocation) invocation).setProceeding(true);
        proceedingInvocation.set(invocation);
    }

    /**
     * 清除当前调用器设置
     */
    public void clearProceedIndicator() {
        proceedingInvocation.set(null);
    }
}